//
//  HOMTrampoline.m
//  HigherOrderMessaging
//
//  Created by Ofri Wolfus on 09/09/05.
//  Copyright 2005 Ofri Wolfus. All rights reserved.
//

#import "HOMTrampoline.h"
#import "NSObject-HOMPrivateAdditions.h"
#import "NSInvocationAdditions.h"
#import "HOMUtilities.h"


@implementation HOMTrampoline

//Returns a new trampoline
+ (id)trampolineWithTarget:(id)aTarget selector:(SEL)aSelector {
	return [[self alloc] initWithTarget:aTarget selector:aSelector];
}

//Create a new trampoline with the passed lifetime
+ (id)trampolineWithTarget:(id)aTarget selector:(SEL)aSelector lifeTime:(unsigned int)numberOfInvocations {
	return [[self alloc] initWithTarget:aTarget selector:aSelector lifeTime:numberOfInvocations];
}

//Init
- (id)initWithTarget:(id)aTarget selector:(SEL)aSelector {
	return [self initWithTarget:aTarget selector:aSelector lifeTime:1];
}

//Init
- (id)initWithTarget:(id)aTarget selector:(SEL)aSelector lifeTime:(unsigned int)numberOfInvocations {
	selector = aSelector;
	lifeTime = numberOfInvocations;
	invocationsCount = 0;
	target = nil;
	[self setTarget:aTarget];
	
	if (lifeTime > 1)
		invocations = [[NSMutableArray alloc] initWithCapacity:lifeTime];
	
	return self;
}

//Clean up
- (void) dealloc {
	[invocations release]; invocations = nil;
	[target release]; target = nil;
	selector = NULL;
	
	[super dealloc];
}


/*- (NSString *)description {
	return [NSString stringWithFormat:@"HOMTrampoline <%p>: {target = %@, selector = %@}",
		(void *)self, target, NSStringFromSelector(selector)];
}*/

//As a trampoline, we respond to anything
- (BOOL)respondsToSelector:(SEL)aSelector {
	return YES;
}

//Forward any message back to our target
- (void)forwardInvocation:(NSInvocation *)anInvocation {
	[anInvocation setTarget:target];
	
	if (invocations) {
		++invocationsCount;
		[invocations addObject:anInvocation];
		
		if (invocationsCount < lifeTime) {
			[anInvocation setNonRetaindReturnValue:self];	//Return ourself
		} else {
			//Forward the message
			[target performSelector:selector
						 withObject:invocations];
			
			//Release ourself
			[self release];
		}
	} else {
		//Forward the message
		[target performSelector:selector
					 withObject:anInvocation];
		
		//Release ourself
		[self release];
	}
}

//We give a chance for our target to return us any method signature it thinks is right by implementing -HOMMethodSignatureForSelector:.
//If our target can not respond to -HOMMethodSignatureForSelector: then we'll try to get a signature by calling
//-methodSignatureForSelector:. If that fails as well, we'll return a signature which returns an id and takes variable arguments of
//type id (variable arguments are not supported by NSMethodSignature in 10.4, but if they'll be someday, we're ready for it).
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
	NSMethodSignature	*result = nil;
	
	if ([target respondsToSelector:@selector(HOMMethodSignatureForSelector:)])
		result = [target HOMMethodSignatureForSelector:aSelector];
	
	//If our target is an instance, we send it a -methodSignatureForSelector: message.
	//If not, it's probably a class, and therefor we first try -methodSignatureForSelector:, and if that fails, -instanceMethodSignatureForSelector:.
	if (!result)
		result = (HOMObjectIsInstance(target) ? [target methodSignatureForSelector:aSelector]
											  : ([target methodSignatureForSelector:aSelector] ?
																							   : [target instanceMethodSignatureForSelector:aSelector]));
	
	//Make sure we return a signature.
	//Instead of hacking our way to create an NSMethodSignature instance, we simply use the signature
	//of NSArray's -initWithObjects: which is exactly what we need.
	if (!result)
		result = [NSArray instanceMethodSignatureForSelector:@selector(initWithObjects:)];
	
	return result;
}

//=====================================================================
//============================= Accessors =============================
//=====================================================================
#pragma mark Accessors

//Set our target
- (void)setTarget:(id)newTarget {
	if (target != newTarget) {
		[target release];
		target = [newTarget retain];
	}
}

//Return our target
- (id)target {
	return target;
}

//Set our selector
- (void)setSelector:(SEL)newSelector {
	selector = newSelector;
}

//Return our selector
- (SEL)selector {
	return selector;
}

@end
